package golf.stockinfo.buysellsimulation;

import golf.stockinfo.buysellsimulation.SimulationData.TransactionType;

import java.io.Serializable;
import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.TreeMap;

import oracle.jdbc.OraclePreparedStatement;

import org.apache.commons.math.stat.regression.SimpleRegression;
import org.joda.time.DateTime;
import org.joda.time.Days;

/**
 * This class is simulation based on USD is the left value of exchange rate
 * @author golf
 *
 */
public class SimulateFOREX_USD implements Serializable{
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 6078098951193569670L;

	private static final double MINIMUM_RUNNING_BALANCE = 0;
	
	public static float INITIAL_CAPITAL = 15000; //USD
	public static float FOREX_LOT_SIZE = 100000; //meaning that 100,000 USD per lot
	public static float FOREX_GEARING_RATIO = 50; //meaning 1:50
	public static float CUT_LOSS_PERCENTAGE_TOTAL = -0.45f;
	public static float CUT_LOSS_PER_LOT = -1000; // e.g. 300 means that we will stop the loss when the remaining margin touches 1700 USD (2,000 - 300)
	
	public static float TAKE_PROFIT_THRESHOLD = 0.15f; //this is the percent on investment to take profit. E.g. 0.2 means when the profit goes up to 20% of the running balance then sell the lots.
	public static boolean TAKE_PROFIT = false;
	
	public static float CAPPED_NUMBER_OF_LOT = 2.1f;
	public static int SMAV = 18;
	
	//The best cut loss is -13,000
	public static Date START_DATE = Date.valueOf("1999-08-15");
	public static int SIMULATION_LENGTH_IN_YEAR = 20;
	
	private static SimpleRegression SIMPLE_REGRESSION = new SimpleRegression();
	
	private TreeMap<DateTime, MarketData> weeklyData;
//	private TreeMap<DateTime, MarketData> dailyData;
	private ArrayList<SimulationData> simulationDataArray = new ArrayList<SimulationData>();
	private static DecimalFormat numberFormat = new DecimalFormat("###,###.00");
	private static DecimalFormat daysFormat = new DecimalFormat("###,###");
	
	private Date dateOfMaxLossForTheWeek = null;
	private Date dateOfMaxGainForTheWeek = null;

	/**
	 * 
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
//		runSingleCase();
		runBulkLoadDB();
//		printSimulationOutputByCodnitions();
	}
	
	private static void runSingleCase(){

		SimulateFOREX_USD ths = new SimulateFOREX_USD();
		ths.execute(true);
		
		System.out.println(ths.simulationDataArray.get(0).getStartDate() + ";" + ths.simulationDataArray.get(ths.simulationDataArray.size() - 1).getEndDate() + 
				";" + numberFormat.format(ths.simulationDataArray.get(ths.simulationDataArray.size() - 1).getBalance()));
		
		DateTime dt1 = new DateTime(ths.simulationDataArray.get(0).getStartDate());
		DateTime dt2 = new DateTime(ths.simulationDataArray.get(ths.simulationDataArray.size() - 1).getEndDate());
		
		System.out.println("======================================================");
		System.out.println("Summary");
		System.out.println("======================================================");
		System.out.println("Investment start date      : " + ths.simulationDataArray.get(0).getStartDate());
		System.out.println("Investment end date        : " + ths.simulationDataArray.get(ths.simulationDataArray.size() - 1).getEndDate());
		System.out.println("Investment time (days)     : " + daysFormat.format(Days.daysBetween(dt1, dt2).getDays()));
		System.out.println("Start Investment amount    : " + numberFormat.format(INITIAL_CAPITAL));
		System.out.println("Total amount of end balance: " + numberFormat.format(ths.simulationDataArray.get(ths.simulationDataArray.size() - 1).getBalance()));
		String roundOffPercent = Float.toString((getApproxEquivalentInterestPerYear(
				INITIAL_CAPITAL, 
				ths.simulationDataArray.get(ths.simulationDataArray.size() - 1).getBalance(), 
				Days.daysBetween(dt1, dt2).getDays())) * 100).split("\\.")[0];
		System.out.println("Approximate Interest/year  : " + roundOffPercent + "%");
		System.out.println("======================================================");
	
	}
	
	private static void runBulkLoadDB(){
		
		Date[] dates = { Date.valueOf("1999-06-06"), Date.valueOf("1999-08-15"), Date.valueOf("1999-10-24"),
				Date.valueOf("2000-01-02"), Date.valueOf("2000-03-12"), Date.valueOf("2000-05-21"), Date.valueOf("2000-07-30"),
				Date.valueOf("2000-10-08"), Date.valueOf("2000-12-17"), Date.valueOf("2001-02-25"), Date.valueOf("2001-05-06"),
				Date.valueOf("2001-07-15"), Date.valueOf("2001-09-23"), Date.valueOf("2001-12-02"), Date.valueOf("2002-02-10"),
				Date.valueOf("2002-04-21"), Date.valueOf("2002-06-30"), Date.valueOf("2002-09-08"), Date.valueOf("2002-11-17"),
				Date.valueOf("2003-01-26"), Date.valueOf("2003-04-06"), Date.valueOf("2003-06-15"), Date.valueOf("2003-08-24"),
				Date.valueOf("2003-11-02"), Date.valueOf("2004-01-11"), Date.valueOf("2004-03-21"), Date.valueOf("2004-05-30"),
				Date.valueOf("2004-08-08"), Date.valueOf("2004-10-17"), Date.valueOf("2004-12-26"), Date.valueOf("2005-03-06"),
				Date.valueOf("2005-05-15"), Date.valueOf("2005-07-24"), Date.valueOf("2005-10-02"), Date.valueOf("2005-12-11"),
				Date.valueOf("2006-02-19"), Date.valueOf("2006-04-30"), Date.valueOf("2006-07-09"), Date.valueOf("2006-09-17"),
				Date.valueOf("2006-11-26"), Date.valueOf("2007-02-04"), Date.valueOf("2007-04-15"), Date.valueOf("2007-06-24"),
				Date.valueOf("2007-09-02"), Date.valueOf("2007-11-11"), Date.valueOf("2008-01-20"), Date.valueOf("2008-03-30"),
				Date.valueOf("2008-06-08"), Date.valueOf("2008-08-17"), Date.valueOf("2008-10-26"), Date.valueOf("2009-01-04"),
				Date.valueOf("2009-03-15"), Date.valueOf("2009-05-24"), Date.valueOf("2009-08-02"), Date.valueOf("2009-10-11"),
				Date.valueOf("2009-12-20"), Date.valueOf("2010-02-28"), Date.valueOf("2010-05-09"), Date.valueOf("2010-07-18"),
				Date.valueOf("2010-09-26"), Date.valueOf("2010-12-05"), Date.valueOf("2011-02-13"), Date.valueOf("2011-04-24"),
				Date.valueOf("2011-07-03"), Date.valueOf("2011-09-11") };
		int count=0, totalCount=0, linIndex = 0;
		long[] xx = null,yy = null;
		
		
		for(int c=0;c<2;c++){
			
			for(float cutLossPercentTotal = -0.05f; cutLossPercentTotal >= -0.50f; cutLossPercentTotal = cutLossPercentTotal - 0.05f){
				for(int cutLossPerLot=-1000 ; cutLossPerLot >= -5000 ; cutLossPerLot = cutLossPerLot - 1000){
					for(float takeProfitThreashold = 0.05f; takeProfitThreashold <= 0.5f; takeProfitThreashold = takeProfitThreashold + 0.05f){
						for(float cappedNoOfLot = 1; cappedNoOfLot <= 10; cappedNoOfLot = cappedNoOfLot + 0.1f){
							for(int smav = 5; smav < 20; smav++){
								
								ArrayList<SimulationData> sims = new ArrayList<SimulationData>();
								for(Date dt:dates){
									
									if(c==0){
										totalCount++;
									}else{
										
										if(xx == null){
											int arrLength = (int) (totalCount / 100000) + 10;
											xx = new long[arrLength];
											yy = new long[arrLength];
										}
										
										SimulateFOREX_USD.START_DATE = dt;
										SimulateFOREX_USD.SMAV = smav;
										SimulateFOREX_USD.CAPPED_NUMBER_OF_LOT = cappedNoOfLot;
										SimulateFOREX_USD.TAKE_PROFIT_THRESHOLD = takeProfitThreashold;
										SimulateFOREX_USD.CUT_LOSS_PER_LOT = cutLossPerLot;
										SimulateFOREX_USD.CUT_LOSS_PERCENTAGE_TOTAL = cutLossPercentTotal;
										
										SimulateFOREX_USD ths = new SimulateFOREX_USD();
										ths.execute(false);
										
										SimulationData simDat = new SimulationData();
										try{
//											simDat.setStartDate(ths.simulationDataArray.get(0).getStartDate());
											simDat.setStartDate(dt);
											simDat.setEndDate(ths.simulationDataArray.get(ths.simulationDataArray.size() - 1).getEndDate());
											simDat.setBalance(ths.simulationDataArray.get(ths.simulationDataArray.size() - 1).getBalance());
//											simDat.setEndIndex(getApproxEquivalentInterestPerYear(INITIAL_CAPITAL, simDat.getBalance_asDouble(), Days.daysBetween(new DateTime(ths.simulationDataArray.get(0).getStartDate()), new DateTime(ths.simulationDataArray.get(ths.simulationDataArray.size() - 1).getEndDate())).getDays()));
										}catch(IndexOutOfBoundsException e){
											
										}
										sims.add(simDat);
										
										if(count % 10000 == 0){
											if(count < 30000){
												System.out.println(new Timestamp(System.currentTimeMillis()).toString() + " ; " + numberFormat.format(count) + " of " + numberFormat.format(totalCount));
											}else if(count >= 30000 && count < 60000){
												xx[linIndex] = count;
												yy[linIndex] = System.currentTimeMillis();
												SIMPLE_REGRESSION.addData(xx[linIndex], yy[linIndex]);
												System.out.println(new Timestamp(System.currentTimeMillis()).toString() + " ; " + numberFormat.format(count) + " of " + numberFormat.format(totalCount));
												linIndex++;
											}else{
												if(count % 100000 == 0){
													if(linIndex > 10){
														//This is to make sure that the slope is based on the last 10 points only
														SIMPLE_REGRESSION.removeData(xx[linIndex-11], yy[linIndex-11]);
													}
													xx[linIndex] = count;
													yy[linIndex] = System.currentTimeMillis();
													SIMPLE_REGRESSION.addData(xx[linIndex], yy[linIndex]);
													linIndex++;
												}
												double m = SIMPLE_REGRESSION.getSlope();
												double b = SIMPLE_REGRESSION.getIntercept();
												long y = (long)(m * totalCount + b);
												System.out.println(new Timestamp(System.currentTimeMillis()).toString() + " ; " + numberFormat.format(count) + " of " + numberFormat.format(totalCount) + " ; ETA=" + new Timestamp(y).toString());
											}
											
											if(count > 1){
												try {
													getConnection().commit();
												} catch (SQLException e) {
													e.printStackTrace();
												}
											}
										}
										count++;
									}

								}

								if(c!=0){
									
									try {
										long conditionId = insertSimConditionTable(
												getConnection(),
												cappedNoOfLot,
												takeProfitThreashold,
												cutLossPerLot,
												cutLossPercentTotal,
												smav);
										insertSimDataTable(
												getConnection(),
												conditionId, 
												sims);
									} catch (SQLException e) {
										e.printStackTrace();
										throw new RuntimeException();
									}
									
								}
							}
							
						}

					}
					
				}
				
			}
			
			//STOP HERE
		}
		
	}
	
	private static void insertSimDataTable(Connection conn, long conditionId, ArrayList<SimulationData> sims) {
		
		try {
			
			PreparedStatement pstmt = conn.prepareStatement(
					"INSERT INTO SIMULATION_OUTPUT_FOREX " +
					"VALUES(?,?,?)");
			
			((OraclePreparedStatement)pstmt).setExecuteBatch (sims.size());
			
			for(SimulationData sim:sims){
				
				pstmt.setLong(1, conditionId);
				pstmt.setDate(2, sim.getStartDate());
				pstmt.setFloat(3, sim.getBalance());
				
				pstmt.executeUpdate();
			}
			
			pstmt.close();
			
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}

	private static long insertSimConditionTable(Connection conn, float cappedNoOfLot, float takeProfitThreashold,
			int cutLossPerLot, float cutLossPercentTotal, int smav2) {
		
		long conditionId = 0;
		try {
			
			//First check if the condition exists
			PreparedStatement pstmt = conn.prepareStatement(
					"SELECT CONDITION_ID " +
					"  FROM SIMULATION_CONDITION_FOREX" +
					" WHERE CAPPED_CONTRACT_NUMBER = ?" +
					" AND PERCENT_MAX_LOSS_ON_BALANCE = ?" +
					" AND LOSS_STOP_PER_LOT = ?" +
					" AND SMAV = ?" +
					" AND PROFIT_TAKING_THREASHOLD = ?");
			
			pstmt.setDouble(1, cappedNoOfLot);
			pstmt.setFloat(2, cutLossPercentTotal);
			pstmt.setInt(3, cutLossPerLot);
			pstmt.setInt(4, smav2);
			pstmt.setFloat(5, takeProfitThreashold);
			
			ResultSet rs = pstmt.executeQuery();
			
			if(rs.next()){
				conditionId = rs.getLong(1);
			}else{
				
				rs.close();
				pstmt.close();
				
				pstmt = conn.prepareStatement("SELECT MAX(CONDITION_ID) FROM SIMULATION_CONDITION_FOREX");
				rs = pstmt.executeQuery();
				if(rs.next()){
					conditionId = rs.getLong(1) + 1;
				}else{
					conditionId = 1;
				}
				rs.close();
				pstmt.close();
				
				pstmt = conn.prepareStatement(
						"INSERT INTO SIMULATION_CONDITION_FOREX " +
						"(CONDITION_ID, CAPPED_CONTRACT_NUMBER, PERCENT_MAX_LOSS_ON_BALANCE, LOSS_STOP_PER_LOT, SMAV, PROFIT_TAKING_THREASHOLD) " +
						"VALUES(?,?,?,?,?,?)");
				
				pstmt.setLong(1, conditionId);
				pstmt.setDouble(2, cappedNoOfLot);
				pstmt.setFloat(3, cutLossPercentTotal);
				pstmt.setInt(4, cutLossPerLot);
				pstmt.setInt(5, smav2);
				pstmt.setFloat(6, takeProfitThreashold);
				pstmt.executeUpdate();
				
				pstmt.close();
				
			}
			
		} catch (SQLException e) {
			e.printStackTrace();
		}
		
		return conditionId;
	}

	private static Connection getConnection() throws SQLException{

		Connection conn = (Connection) GlobalCache.getCache(serialVersionUID);

		if (conn == null) {
			conn = DriverManager.getConnection("jdbc:oracle:thin:@172.16.115.131:1521/finance", "finance", "finance");
//			conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "finance", "finance");
			conn.setAutoCommit(false);
			GlobalCache.addCache(serialVersionUID, conn);
		}
		
		return conn;

	}
	
	public void execute(boolean printDetailedSimulation){
		
		boolean isInvested = false;

		double runningBalance = INITIAL_CAPITAL;
		
		try {
			
			Connection conn = getConnection();
			
			weeklyData = SimulationCommonUtils.loadMarketDataFromDB(conn, SMAV, "OANDA", "EURUSD", "WEEKLY");
			
			Iterator<DateTime> itrWeekly = weeklyData.keySet().iterator();
			MarketData thisWeek = null;
			
			SimulationData simulationItem = null;
			
			if(itrWeekly.hasNext()){
				thisWeek = weeklyData.get(itrWeekly.next());
			}

			DateTime stopDate = new DateTime(START_DATE).plusYears(SIMULATION_LENGTH_IN_YEAR).plusMonths(3);
			DateTime currDate = new DateTime(thisWeek.getDate());


			//Start looping through the weekly data to find a start-point, either to short or buy SET50
			while(itrWeekly.hasNext()
					&& currDate.isBefore(stopDate.getMillis())){
				
				while(itrWeekly.hasNext()
						&& (currDate.isBefore(START_DATE.getTime()) || currDate.isEqual(START_DATE.getTime()))){
					//Loop to the start point
					thisWeek = weeklyData.get(itrWeekly.next());
					currDate = new DateTime(thisWeek.getDate());
				}
				
				if( ! isInvested ){

					if(isUnder(thisWeek)){
						//This means the candle stick is under the SMAV line
						simulationItem = new SimulationData();
						simulationItem.setTypeOfTransaction(TransactionType.SHORT_TEFX);
						
						isInvested = true;

					}else if(isAbove(thisWeek)){
						//This means the candle stick is above the SMAV line
						simulationItem = new SimulationData();
						simulationItem.setTypeOfTransaction(TransactionType.LONG_TFEX);
						
						isInvested = true;

					}else{
						//This means the candle stick is in the middle of the line, need to skip run
						thisWeek = weeklyData.get(itrWeekly.next());
						currDate = new DateTime(thisWeek.getDate());
					}
					
					if(isInvested){
						
						double noOfLot = Math.abs((runningBalance * CUT_LOSS_PERCENTAGE_TOTAL) / CUT_LOSS_PER_LOT);
						while(noOfLot * FOREX_LOT_SIZE / FOREX_GEARING_RATIO > runningBalance){
							noOfLot = noOfLot - 0.1f;
						}
						
						if(noOfLot > CAPPED_NUMBER_OF_LOT){
							simulationItem.setNumberOfForexLot(CAPPED_NUMBER_OF_LOT);
						}else{
							simulationItem.setNumberOfForexLot(SimulationCommonUtils.roundOff(noOfLot, 1));
						}

						simulationItem.setStartDate(thisWeek.getDate());
						simulationItem.setStartIndex(getForexClosingIndexOfWeek(thisWeek, conn));
						thisWeek = weeklyData.get(itrWeekly.next());
						currDate = new DateTime(thisWeek.getDate());
					}
					
				}else{
					
					boolean initSimItem = false;
					
					double maxLossThisWeekPerLot = getMaxProfitOrLossPerLotDuringWeek(conn, true, simulationItem, thisWeek);
					double maxLossThisWeek = maxLossThisWeekPerLot * simulationItem.getNumberOfForexLot();
					
					double maxGainThisWeekPerLot = getMaxProfitOrLossPerLotDuringWeek(conn, false, simulationItem, thisWeek);
					double maxGainThisWeek = maxGainThisWeekPerLot * simulationItem.getNumberOfForexLot();
					
					if(maxLossThisWeek < simulationItem.getMaximumPointLossForPeriod()){
						simulationItem.setMaximumPointLossForPeriod(maxLossThisWeek);
					}
					
					if(maxGainThisWeek > simulationItem.getMaximumPointGainForPeriod_asDouble()){
						simulationItem.setMaximumPointGainForPeriod(maxGainThisWeek);
					}
					
					if((maxLossThisWeek < CUT_LOSS_PER_LOT
								&& simulationItem.getCutLossAmount() == 0
								&& dateOfMaxLossForTheWeek.before(dateOfMaxGainForTheWeek)
								&& maxGainThisWeek/runningBalance >= TAKE_PROFIT_THRESHOLD
								&& TAKE_PROFIT)
							|| (maxLossThisWeek < CUT_LOSS_PER_LOT
								&& simulationItem.getCutLossAmount() == 0
								&& (maxGainThisWeek/runningBalance < TAKE_PROFIT_THRESHOLD
										|| ! TAKE_PROFIT))){
						//Cut loss
						simulationItem.setCutLossDate(thisWeek.getDate());
						simulationItem.setCutLossAmount(simulationItem.getNumberOfForexLot() * CUT_LOSS_PER_LOT);
						simulationItem.setEndDate(thisWeek.getDate());
						simulationItem.setMaximumPointLossForPeriod(simulationItem.getNumberOfForexLot() * CUT_LOSS_PER_LOT);
						runningBalance = runningBalance + (simulationItem.getNumberOfForexLot() * CUT_LOSS_PER_LOT);
						simulationItem.setBalance(runningBalance);
						
					}else if(TAKE_PROFIT
							&& maxGainThisWeek/runningBalance >= TAKE_PROFIT_THRESHOLD
							&& simulationItem.getCutLossAmount() == 0){
						
						simulationItem.setTakeProfit(true);
						simulationItem.setEndDate(thisWeek.getDate());
						runningBalance = runningBalance + (TAKE_PROFIT_THRESHOLD * runningBalance);
						simulationItem.setBalance(runningBalance);
						
						initSimItem = true;
						thisWeek = weeklyData.get(itrWeekly.next());
						currDate = new DateTime(thisWeek.getDate());

					}else if(hasCutSMAV(simulationItem, thisWeek)){
						
						simulationItem.setEndDate(thisWeek.getDate());
						simulationItem.setEndIndex(getForexClosingIndexOfWeek(thisWeek, conn));
						if(simulationItem.getCutLossAmount() == 0){
							//If it was loss cutting, no need to put anything, just end the simulation item
							runningBalance = runningBalance + (getProfitOrLossPerLotAtEndOfWeek(simulationItem, thisWeek, conn) * simulationItem.getNumberOfForexLot_asDouble());
							simulationItem.setBalance(runningBalance);
						}
						initSimItem = true;
					}
					
					//Check if we should quite the market
					if(runningBalance < MINIMUM_RUNNING_BALANCE){
						if(isInvested){
							simulationDataArray.add(simulationItem);
						}
						break;
					}
					
					if(initSimItem){
						simulationDataArray.add(simulationItem);
						isInvested = false;
					}else{
						
						thisWeek = weeklyData.get(itrWeekly.next());
						currDate = new DateTime(thisWeek.getDate());
						
					}
					
				}
				
			}
			
			if(printDetailedSimulation){
				System.out.println(
						"StartDate;" +
						"StartIndex;" +
						"Type;" +
						"EndDate;" +
						"EndIndex;" +
						"No of FOREX Lot;" +
						"MaxLoss;" +
						"TakeProfit;" +
						"CutLossDate;" +
						"CutLossAmt;" +
						"Balance"
						);
				for(SimulationData simulateItem:simulationDataArray){
					System.out.println(
							simulateItem.getStartDate() + ";" 
							+ simulateItem.getStartIndex() + ";"
							+ simulateItem.getTypeOfTransaction() + ";"
							+ simulateItem.getEndDate() + ";" 
							+ simulateItem.getEndIndex() + ";" 
							+ simulateItem.getNumberOfForexLot() + ";"
							+ simulateItem.getMaximumPointLossForPeriod() + ";" 
							+ simulateItem.isTakeProfit() + ";"
							+ simulateItem.getCutLossDate()	+  ";" 
							+ numberFormat.format(simulateItem.getCutLossAmount()) + ";" 
							+ numberFormat.format(simulateItem.getBalance())
							);
				}
				
				System.out.println("================ Done ================");
			}
			
		} catch (SQLException e) {
			e.printStackTrace();
			throw new RuntimeException(e.getMessage());
		}
	}
	
	private boolean isUnder(MarketData thisWeek) {
		
		if(thisWeek.getHighIndex() < thisWeek.getCloseIndexSMAV()){
			return true;
		}else{
			return false;
		}
	}

	private boolean isAbove(MarketData thisWeek) {
		
		if(thisWeek.getLowIndex() > thisWeek.getCloseIndexSMAV()){
			return true;
		}else{
			return false;
		}
	}

	private static float getApproxEquivalentInterestPerYear(float initialCapital, float endedBalance, long days){
		
		int capPeriod = 180;
		float initInterest = 0.001f;
		float newEndedBalance = initialCapital;
		
		boolean reverseCalc = false;
		if(endedBalance < initialCapital){
			reverseCalc = true;
			float tmpFlt = initialCapital;
			initialCapital = endedBalance;
			endedBalance = tmpFlt;
		}
		
		while(Math.abs((endedBalance - newEndedBalance)/endedBalance) > 0.002){
			
			int i=1;
			newEndedBalance = initialCapital;
			do{
				newEndedBalance = newEndedBalance * (1 + (initInterest / 2));
				i = i+capPeriod;
			}while(i <= days);
			
			initInterest += 0.0001f;
		}
		
		if(reverseCalc){
			return (-1 * initInterest);
		}else{
			return initInterest;
		}
	}
	
	enum SideChangingStrategy{
		OPEN_CLOSE, HIGH_LOW
	}
	
	enum SideChangingMargin{
		FULL_STICK, PARTIAL_STICK
	}
	
	/**
	 * 
	 * @param conn
	 * @param getLoss - true means to get max loss, otherwise get max profit
	 * @param simData
	 * @param thisWeek
	 * @return
	 * @throws SQLException
	 */
	private double getMaxProfitOrLossPerLotDuringWeek(
			Connection conn, boolean getLoss, SimulationData simData, MarketData thisWeek) throws SQLException{
		
		double output = 0;
		
		long lowestCacheKey = 900000000000000l + thisWeek.getDate().getTime();
		long highestCacheKey = 910000000000000l + thisWeek.getDate().getTime();
		long lowestDateCacheKey = 920000000000000l + thisWeek.getDate().getTime();
		long highestDateCacheKey = 930000000000000l + thisWeek.getDate().getTime();
		
		Double lowestDuringWeek = (Double) GlobalCache.getCache(lowestCacheKey);
		Double highestDuringWeek = (Double) GlobalCache.getCache(highestCacheKey);
		Date lowestDate = (Date) GlobalCache.getCache(lowestDateCacheKey);
		Date highestDate = (Date) GlobalCache.getCache(highestDateCacheKey);
		
		if(lowestDuringWeek == null
				|| highestDuringWeek == null){
			
			PreparedStatement pstmt = conn.prepareStatement(
					"SELECT MIN(T.LOW), MAX(T.HIGH)" +
					"  FROM PRICE_HIST T" +
					" WHERE T.TRADING_DATE >= ?" +
					"   AND T.TRADING_DATE < ?" +
					"   AND T.CURRENCY_PAIR = 'EURUSD'" +
					"   AND T.TRADING_TYPE = 'DAILY'" +
					"   AND T.MARKET_NAME = 'FOREX'");
			
			pstmt.setDate(1, thisWeek.getDate());
			pstmt.setDate(2, thisWeek.getNextDate());
			
			ResultSet rs = pstmt.executeQuery();
			
			if(rs.next()){
				
				highestDuringWeek = rs.getDouble(2);
				lowestDuringWeek = rs.getDouble(1);
				
			}

			rs.close();
			pstmt.close();
			
			if(highestDuringWeek == 0 || lowestDuringWeek == 0){
				//Something is wrong, return 0
				return 0;
			}
			
			GlobalCache.addCache(highestCacheKey, highestDuringWeek);
			GlobalCache.addCache(lowestCacheKey, lowestDuringWeek);
			
		}
		
		if(lowestDate == null
				|| highestDate == null){
			PreparedStatement ps = conn.prepareStatement(
					"SELECT T.TRADING_DATE" +
					"  FROM PRICE_HIST T" +
					" WHERE T.TRADING_DATE >=?" +
					"   AND T.TRADING_DATE < ?" +
					"   AND T.LOW = ?" +
					"   AND T.CURRENCY_PAIR = 'EURUSD'" +
					"   AND T.TRADING_TYPE = 'DAILY'" +
					"   AND T.MARKET_NAME = 'FOREX'" +
					" ORDER BY 1");
			
			ps.setDate(1, thisWeek.getDate());
			ps.setDate(2, thisWeek.getNextDate());
			ps.setDouble(3, lowestDuringWeek);
			
			ResultSet rs = ps.executeQuery();
			
			if(rs.next()){
				lowestDate = rs.getDate(1);
			}
			
			if(lowestDate == null){
				throw new RuntimeException(
						"Error Can't find lowest date for " + thisWeek.getDate().toString() + " with low point at " + lowestDuringWeek);
			}else{
				GlobalCache.addCache(lowestDateCacheKey, lowestDate);
			}
			
			rs.close();
			ps.close();
			
			ps = conn.prepareStatement(
					"SELECT T.TRADING_DATE" +
					"  FROM PRICE_HIST T" +
					" WHERE T.TRADING_DATE >=?" +
					"   AND T.TRADING_DATE < ?" +
					"   AND T.HIGH = ?" +
					"   AND T.CURRENCY_PAIR = 'EURUSD'" +
					"   AND T.TRADING_TYPE = 'DAILY'" +
					"   AND T.MARKET_NAME = 'FOREX'" +
					" ORDER BY 1");
			
			ps.setDate(1, thisWeek.getDate());
			ps.setDate(2, thisWeek.getNextDate());
			ps.setDouble(3, highestDuringWeek);
			
			rs = ps.executeQuery();
			
			if(rs.next()){
				highestDate = rs.getDate(1);
			}
			
			if(highestDate == null){
				throw new RuntimeException(
						"Error Can't find highest date for " + thisWeek.getDate().toString() + " with high point at " + highestDuringWeek);
			}else{
				GlobalCache.addCache(highestDateCacheKey, highestDate);
			}
			
			rs.close();
			ps.close();
			
		}
		
		if(getLoss){
			if(simData.getTypeOfTransaction().equals(TransactionType.LONG_TFEX)){
				output = (lowestDuringWeek - simData.getStartIndex())/simData.getStartIndex() * FOREX_LOT_SIZE;
				dateOfMaxLossForTheWeek = lowestDate;
			}else if(simData.getTypeOfTransaction().equals(TransactionType.SHORT_TEFX)){
				output = (simData.getStartIndex() - highestDuringWeek)/simData.getStartIndex() * FOREX_LOT_SIZE;
				dateOfMaxLossForTheWeek = highestDate;
			}
		}else{
			if(simData.getTypeOfTransaction().equals(TransactionType.LONG_TFEX)){
				output = (highestDuringWeek - simData.getStartIndex())/simData.getStartIndex() * FOREX_LOT_SIZE;
				dateOfMaxGainForTheWeek = highestDate;
			}else if(simData.getTypeOfTransaction().equals(TransactionType.SHORT_TEFX)){
				output = (simData.getStartIndex() - lowestDuringWeek)/simData.getStartIndex() * FOREX_LOT_SIZE;
				dateOfMaxGainForTheWeek = lowestDate;
			}
		}
		
		
		return output;
	}
	
	private double getForexClosingIndexOfWeek(MarketData thisWeek, Connection conn) throws SQLException{
		
		long cacheKey = 940000000000000l + thisWeek.getDate().getTime();
		
		Double closeIndex = (Double) GlobalCache.getCache(cacheKey);
		
		if(closeIndex != null){
			return closeIndex;
		}
		
		PreparedStatement pstmt = conn.prepareStatement(
				"SELECT CLOSE" +
				"  FROM PRICE_HIST " +
				" WHERE TRADING_DATE = ?" +
				"   AND CURRENCY_PAIR = 'EURUSD'" +
				"   AND TRADING_TYPE = 'WEEKLY'" +
				"   AND MARKET_NAME = 'FOREX'");
		
		pstmt.setDate(1, thisWeek.getDate()); 
		
		ResultSet rs = pstmt.executeQuery();
		
		if(rs.next()){
			closeIndex = rs.getDouble("CLOSE");
		}
		
		rs.close();
		pstmt.close();
		
		if(closeIndex == null){
			pstmt = conn.prepareStatement(
					"SELECT CLOSE" +
					"  FROM PRICE_HIST" +
					" WHERE TRADING_DATE = (SELECT MAX(TRADING_DATE)" +
					"						  FROM PRICE_HIST" +
					"						 WHERE CURRENCY_PAIR = 'EURUSD'" +
					"						   AND TRADING_TYPE = 'WEEKLY'" +
					"						   AND MARKET_NAME = 'FOREX'" +
					"						   AND TRADING_DATE < ?)" +
					"   AND CURRENCY_PAIR = 'EURUSD'" +
					"   AND TRADING_TYPE = 'WEEKLY'" +
					"   AND MARKET_NAME = 'FOREX'");
			
			pstmt.setDate(1, thisWeek.getDate());
			
			rs = pstmt.executeQuery();
			if(rs.next()){
				closeIndex = rs.getDouble("CLOSE");
			}
			rs.close();
			pstmt.close();
		}
		
		GlobalCache.addCache(cacheKey, closeIndex);
//		System.out.println(thisWeek.getDate());
		return closeIndex;
	}

	private double getProfitOrLossPerLotAtEndOfWeek(SimulationData simData, MarketData thisWeek, Connection conn) throws SQLException{
		
		double output = 0;
		double closeIndex = getForexClosingIndexOfWeek(thisWeek, conn);

		if(simData.getTypeOfTransaction().equals(TransactionType.LONG_TFEX)){
			output = (closeIndex - simData.getStartIndex()) / simData.getStartIndex()  * FOREX_LOT_SIZE;
		}else if(simData.getTypeOfTransaction().equals(TransactionType.SHORT_TEFX)){
			output = (simData.getStartIndex() - closeIndex) / simData.getStartIndex() * FOREX_LOT_SIZE;
		}
		
		return output;
	}
	
	private boolean hasCutSMAV(SimulationData simulationItem, MarketData thisWeek) {
		
		if(simulationItem.getTypeOfTransaction().equals(TransactionType.LONG_TFEX)
				|| simulationItem.getTypeOfTransaction().equals(TransactionType.BUY_SET50)){
			if(isUnder(thisWeek)){
				return true;
			}
		}else if(simulationItem.getTypeOfTransaction().equals(TransactionType.SHORT_TEFX)){
			if(isAbove(thisWeek)){
				return true;
			}
		}
		return false;
	}

	private static void printSimulationOutputByCodnitions(){
		
		Connection conn;
		long[] conditions = {488879};
		
		Date[] dateArr = { Date.valueOf("1999-06-06"), Date.valueOf("1999-08-15"), Date.valueOf("1999-10-24"),
				Date.valueOf("2000-01-02"), Date.valueOf("2000-03-12"), Date.valueOf("2000-05-21"), Date.valueOf("2000-07-30"),
				Date.valueOf("2000-10-08"), Date.valueOf("2000-12-17"), Date.valueOf("2001-02-25"), Date.valueOf("2001-05-06"),
				Date.valueOf("2001-07-15"), Date.valueOf("2001-09-23"), Date.valueOf("2001-12-02"), Date.valueOf("2002-02-10"),
				Date.valueOf("2002-04-21"), Date.valueOf("2002-06-30"), Date.valueOf("2002-09-08"), Date.valueOf("2002-11-17"),
				Date.valueOf("2003-01-26"), Date.valueOf("2003-04-06"), Date.valueOf("2003-06-15"), Date.valueOf("2003-08-24"),
				Date.valueOf("2003-11-02"), Date.valueOf("2004-01-11"), Date.valueOf("2004-03-21"), Date.valueOf("2004-05-30"),
				Date.valueOf("2004-08-08"), Date.valueOf("2004-10-17"), Date.valueOf("2004-12-26"), Date.valueOf("2005-03-06"),
				Date.valueOf("2005-05-15"), Date.valueOf("2005-07-24"), Date.valueOf("2005-10-02"), Date.valueOf("2005-12-11"),
				Date.valueOf("2006-02-19"), Date.valueOf("2006-04-30"), Date.valueOf("2006-07-09"), Date.valueOf("2006-09-17"),
				Date.valueOf("2006-11-26"), Date.valueOf("2007-02-04"), Date.valueOf("2007-04-15"), Date.valueOf("2007-06-24"),
				Date.valueOf("2007-09-02"), Date.valueOf("2007-11-11"), Date.valueOf("2008-01-20"), Date.valueOf("2008-03-30"),
				Date.valueOf("2008-06-08"), Date.valueOf("2008-08-17"), Date.valueOf("2008-10-26"), Date.valueOf("2009-01-04"),
				Date.valueOf("2009-03-15"), Date.valueOf("2009-05-24"), Date.valueOf("2009-08-02"), Date.valueOf("2009-10-11"),
				Date.valueOf("2009-12-20"), Date.valueOf("2010-02-28"), Date.valueOf("2010-05-09"), Date.valueOf("2010-07-18"),
				Date.valueOf("2010-09-26"), Date.valueOf("2010-12-05"), Date.valueOf("2011-02-13"), Date.valueOf("2011-04-24"),
				Date.valueOf("2011-07-03"), Date.valueOf("2011-09-11") };
		
		try {
			conn = DriverManager.getConnection("jdbc:oracle:thin:@172.16.115.131:1521/finance", "finance", "finance");
			conn.setAutoCommit(false);
			
			for(long condition:conditions){
				PreparedStatement ps = conn.prepareStatement(
						" select CAPPED_CONTRACT_NUMBER" +
						" 		,PERCENT_MAX_LOSS_ON_BALANCE" +
						" 		,LOSS_STOP_PER_LOT" +
						" 		,SMAV" +
						" 		,PROFIT_TAKING_THREASHOLD" +
						"  FROM SIMULATION_CONDITION_FOREX" +
						" WHERE CONDITION_ID = ?" 
						);
				
				ps.setLong(1, condition);
				ResultSet rs = ps.executeQuery();
				if(rs.next()){
					
					System.out.print(rs.getDouble(1) + "|");
					System.out.print(rs.getDouble(2) + "|");
					System.out.print(rs.getDouble(3) + "|");
					System.out.print(rs.getInt(4) + "|");
					System.out.print(rs.getDouble(5));
					
					for(Date dt:dateArr){
						PreparedStatement ps2 = conn.prepareStatement(
								"SELECT BALANCE" +
								"  FROM SIMULATION_OUTPUT_FOREX" +
								" WHERE CONDITION_ID = ?" +
								"   AND START_DATE = ?");
						ps2.setLong(1, condition);
						ps2.setDate(2, dt);
						
						ResultSet rs2 = ps2.executeQuery();
						
						if(rs2.next()){
							System.out.print("|" + numberFormat.format(rs2.getDouble("BALANCE")));
						}else{
							System.out.print("|0");
						}
						
						rs2.close();
						ps2.close();
					}
					System.out.println("");
				}
				
				rs.close();
				ps.close();
			}
			
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
}
